﻿using System;
using System.Net;
using System.Text;

namespace lmsft_sip {
	using common;
	using i_core;
	using lmsft;
	using lmsft.RTP;
	using lmsft.SDP;
	using lmsft.SIP;
	using lmsft.SIP.Message;
	using lmsft.SIP.Stack;

	public class sip_stack_req {
		public i_log log;


		public void stack_error(object sender, ExceptionEventArgs e) {
			log.Error("sip stack has unhandled error.", e.Exception);
		}

		public void stack_validate_request(object sender, SIP_ValidateRequestEventArgs e) {
			//log.Debug($"receive validate request: {e.Request.RequestLine.Method}");
		}

		/// <summary>
		/// Is called when sip stack has received request.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		public void stack_request_received(object sender, SIP_RequestReceivedEventArgs e) {
			var s_stack = sender as SIP_Stack_Extension;

			try {
				switch (e.Request.RequestLine.Method) {
					case SIP_Methods.CANCEL:
						/* RFC 3261 9.2.
						If the UAS did not find a matching transaction for the CANCEL
						according to the procedure above, it SHOULD respond to the CANCEL
						with a 481 (Call Leg/Transaction Does Not Exist).
						Regardless of the method of the original request, as long as the
						CANCEL matched an existing transaction, the UAS answers the CANCEL
						request itself with a 200 (OK) response.
						*/

						try {
							var trToCancel = s_stack.TransactionLayer.MatchCancelToTransaction(e.Request);
							if (trToCancel != null) {
								trToCancel.Cancel();

								e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request));
							} else {
								e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request));
							}
						} catch (Exception ex) {
							log.Warn("process cancel request error:" + ex.Message);
						}

						break;
					case SIP_Methods.INVITE:
						//we don't accept two or more calls at the same time.
						//if (s_stack.sip_call != null && s_stack.sip_call.State != SIP_CALL_STATE.Calling && s_stack.sip_call.State != SIP_CALL_STATE.Active) {
						//    log.Warn($"we don't accept two or more calls at the same time. the call state is {s_stack.sip_call.State}");
						//    e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable + ": we don't accept two or more calls at the same time", e.Request));
						//    return;
						//}

						// We don't accept SDP offerless calls.
						if (e.Request.ContentType == null || e.Request.ContentType.ToLower().IndexOf("application/sdp") == -1) {
							e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable + ": We don't accpet SDP offerless calls.", e.Request));
							return;
						}

						var sdpOffer = SDP_Message.Parse(Encoding.UTF8.GetString(e.Request.Data));

						// Check if we can accept any media stream.
						log.Debug("check media support or not");
						var canAccept = false;
						foreach (var media in sdpOffer.MediaDescriptions) {
							if (new rtp_media().CanSupportMedia(media)) {
								canAccept = true;
								break;
							}
						}
						if (!canAccept) {
							e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x606_Not_Acceptable, e.Request));
							return;
						}

						log.Debug("send 180 to remote");
						var responseRinging = s_stack.CreateResponse(SIP_ResponseCodes.x180_Ringing, e.Request, e.Flow);
						responseRinging.To.Tag = SIP_Utils.CreateTag();
						e.ServerTransaction.SendResponse(responseRinging);

						var dialog = (SIP_Dialog_Invite)s_stack.TransactionLayer.GetOrCreateDialog(e.ServerTransaction, responseRinging);

						if (!e.Request.caller_number.IsEmpty() && !e.Request.callee_number.IsEmpty()) {
							if (!s_stack.sip_new_comming(e.Request.caller_number, e.Request.callee_number)) {
								e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x600_Busy_Everywhere, e.Request));
								return;
							}
						}
						var rtpMultimediaSession = new RTP_MultimediaSession(RTP_Utils.GenerateCNAME());

						log.Debug("Build local SDP template");
						var sdpLocal = new SDP_Message {
							Version = "0",
							SessionName = "SIP Call"
						};
						sdpLocal.Origin = new SDP_Origin("-", sdpLocal.GetHashCode(), 1, "IN", "IP4", Dns.GetHostAddresses("")[0].ToString());
						sdpLocal.Times.Add(new SDP_Time(0, 0));

						log.Debug("create sip call");
						s_stack.sip_call = new SIP_CALL(dialog, rtpMultimediaSession, sdpLocal);
						s_stack.sip_call.StateChanged += new EventHandler(m_pCall_StateChanged);
						m_pCall_StateChanged(s_stack.sip_call, new EventArgs());

						log.Debug("process offer media");
						s_stack.answer_sdp = new anwser_sdp_stack {
							log = log
						};
						s_stack.answer_sdp.build(s_stack, dialog, e.ServerTransaction, rtpMultimediaSession, sdpOffer, sdpLocal);

						break;
						break;
					case SIP_Methods.ACK:
						break;
					case SIP_Methods.BYE:
						/* RFC 3261 15.1.2.
						If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481
						(Call/Transaction Does Not Exist) response and pass that to the server transaction.
						*/
						// Currently we match BYE to dialog and it processes it,
						// so BYE what reaches here doesnt match to any dialog.
						//log.Debug("receive a bye request");
						e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request));

						break;
					case SIP_Methods.REGISTER:
						log.Debug("receive a register request");

						break;
					case SIP_Methods.NOTIFY:
						//log.Debug("receive a notify request");

						e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request));
						break;
					case SIP_Methods.MESSAGE:
						//log.Debug("receive a message request");
						//log.Debug($"message:{e.Request.RequestLine}");
						break;
					default:
						//Log.Error(e.Request.RequestLine.Method.ToString());
						e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, e.Request));
						break;
				}
			} catch (Exception ex) {
				log.Error("build answer sdp error", ex);
				e.ServerTransaction.SendResponse(s_stack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error, e.Request));
			}
		}

		/// <summary>
		/// Is called when sip stack has received response.
		/// </summary>eeeeeeeee
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		public void stack_response_received(object sender, SIP_ResponseReceivedEventArgs e) {
			log.Debug("stack response recieve " + Encoding.GetEncoding("UTF-8").GetString(e.Response.Data));
		}

		/// <summary>
		/// Is called when call state changed
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pCall_StateChanged(object sender, EventArgs e) {

			var sip_call = sender as SIP_CALL;
			if (sip_call == null)
				return;

			switch (sip_call.State) {
				case SIP_CALL_STATE.Active:
					//SIP_Request info = sip_call.Dialog.CreateRequest(SIP_Methods.INFO);
					//info.ContentType = "application/dtmf-relay";
					//info.Data = Encoding.ASCII.GetBytes("Signal=9" + "\r\n" + "Duration=160" + "\r\n");

					//SIP_RequestSender infoSender = sip_call.Dialog.CreateRequestSender(info);
					//infoSender.Start();
					break;
				case SIP_CALL_STATE.Terminated:
					break;
				case SIP_CALL_STATE.Terminating:
					break;
				case SIP_CALL_STATE.Disposed:
					if (sip_call != null)
						sip_call.Dispose();
					sip_call = null;
					break;
				case SIP_CALL_STATE.Calling:
					break;
				default:
					break;
			}
		}


		/// <summary>
		/// This method takes care of ACK sending and 2xx response retransmission ACK sending.
		/// </summary>
		/// <param name="dialog">SIP dialog.</param>
		/// <param name="transaction">SIP client transaction.</param>
		private void HandleAck(SIP_Dialog dialog, SIP_ClientTransaction transaction) {
			if (dialog == null) {
				throw new ArgumentNullException("dialog");
			}
			if (transaction == null) {
				throw new ArgumentNullException("transaction");
			}

			/* RFC 3261 6.
				The ACK for a 2xx response to an INVITE request is a separate transaction.
			  
			   RFC 3261 13.2.2.4.
				The UAC core MUST generate an ACK request for each 2xx received from
				the transaction layer.  The header fields of the ACK are constructed
				in the same way as for any request sent within a dialog (see Section
				12) with the exception of the CSeq and the header fields related to
				authentication.  The sequence number of the CSeq header field MUST be
				the same as the INVITE being acknowledged, but the CSeq method MUST
				be ACK.  The ACK MUST contain the same credentials as the INVITE.  If
				the 2xx contains an offer (based on the rules above), the ACK MUST
				carry an answer in its body.
			*/

			var via = new SIP_t_ViaParm {
				ProtocolName = "SIP",
				ProtocolVersion = "2.0",
				ProtocolTransport = transaction.Flow.Transport,
				SentBy = new HostEndPoint(transaction.Flow.LocalEP),
				Branch = SIP_t_ViaParm.CreateBranch(),
				RPort = 0
			};

			var ackRequest = dialog.CreateRequest(SIP_Methods.ACK);
			ackRequest.Via.AddToTop(via.ToStringValue());
			ackRequest.CSeq = new SIP_t_CSeq(transaction.Request.CSeq.SequenceNumber, SIP_Methods.ACK);
			// Authorization
			foreach (SIP_HeaderField h in transaction.Request.Authorization.HeaderFields) {
				ackRequest.Authorization.Add(h.Value);
			}
			// Proxy-Authorization 
			foreach (SIP_HeaderField h in transaction.Request.ProxyAuthorization.HeaderFields) {
				ackRequest.Authorization.Add(h.Value);
			}

			// Send ACK.
			SendAck(dialog, ackRequest);

			// Start receive 2xx retransmissions.
			transaction.ResponseReceived += delegate (object sender, SIP_ResponseReceivedEventArgs e) {
				if (dialog.State == SIP_DialogState.Disposed || dialog.State == SIP_DialogState.Terminated) {
					return;
				}

				// Don't send ACK for forked 2xx, our sent BYE(to all early dialogs) or their early timer will kill these dialogs.
				// Send ACK only to our accepted dialog 2xx response retransmission.
				if (e.Response.From.Tag == ackRequest.From.Tag && e.Response.To.Tag == ackRequest.To.Tag) {
					SendAck(dialog, ackRequest);
				}
			};
		}

		/// <summary>
		/// Sends ACK to remote-party.
		/// </summary>
		/// <param name="dialog">SIP dialog.</param>
		/// <param name="ack">SIP ACK request.</param>
		private void SendAck(SIP_Dialog dialog, SIP_Request ack) {
			if (dialog == null) {
				throw new ArgumentNullException("dialog");
			}
			if (ack == null) {
				throw new ArgumentNullException("ack");
			}

			try {
				// Try existing flow.
				dialog.Flow.Send(ack);

				// Log
				if (dialog.Stack.Logger != null) {
					var ackBytes = ack.ToByteData();

					dialog.Stack.Logger.AddWrite(
						dialog.ID,
						null,
						ackBytes.Length,
						"Request [DialogID='" + dialog.ID + "';" + "method='" + ack.RequestLine.Method + "'; cseq='" + ack.CSeq.SequenceNumber + "'; " +
						"transport='" + dialog.Flow.Transport + "'; size='" + ackBytes.Length + "'] sent '" + dialog.Flow.LocalEP + "' -> '" + dialog.Flow.RemoteEP + "'.",
						dialog.Flow.LocalEP,
						dialog.Flow.RemoteEP,
						ackBytes
					);
				}
			} catch {
				/* RFC 3261 13.2.2.4.
					Once the ACK has been constructed, the procedures of [4] are used to
					determine the destination address, port and transport.  However, the
					request is passed to the transport layer directly for transmission,
					rather than a client transaction.
				*/
				try {
					dialog.Stack.TransportLayer.SendRequest(ack);
				} catch (Exception x) {
					// Log
					if (dialog.Stack.Logger != null) {
						dialog.Stack.Logger.AddText("Dialog [id='" + dialog.ID + "'] ACK send for 2xx response failed: " + x.Message + ".");
					}
				}
			}
		}

	}
}
